

# file_operations.py
import tkinter as tk
from tkinter import filedialog, messagebox
import os
import json
import cv2
from PIL import Image
from ui_utils import display_image_on_canvas
import numpy as np

def toggle_video_play(shared_data, app):
    """点击视频画面，暂停/恢复播放，并显示/隐藏“已暂停”提示"""
    if not shared_data.cap or not shared_data.cap.isOpened():
        return

    canvas = shared_data.ui_elements["source_canvas"]
    shared_data.video_playing = not shared_data.video_playing

    if shared_data.video_playing:
        app.show_status("视频已恢复播放")
        canvas.delete("pause_text")
        update_video_frame(shared_data, app)
    else:
        app.show_status("视频已暂停")
        width = canvas.winfo_width()
        height = canvas.winfo_height()
        if width <= 1: width = 600
        if height <= 1: height = 400

        canvas.delete("pause_text")

        # 背景矩形
        x1, y1 = width//2 - 130, height//2 - 50
        x2, y2 = width//2 + 130, height//2 + 50
        canvas.create_rectangle(x1, y1, x2, y2, fill="#333333", outline="#0066cc", width=3, tags="pause_text")
        
        # 文字
        canvas.create_text(
            width // 2,
            height // 2,
            text="⏸️ 已暂停\n点击恢复播放",
            fill="white",
            font=("SimHei", 16, "bold"),
            justify="center",
            anchor="center",
            tags="pause_text"
        )

def bind_menu_events(app):
    """绑定菜单鼠标事件"""
    app.current_submenu = None
    app.menubar.bind("<Motion>", app.on_menu_motion)

    for submenu in [app.file_menu, app.model_menu, app.data_menu,
                    app.train_menu, app.infer_menu, app.about_menu]:
        submenu.bind("<Leave>", app.on_submenu_leave)

    app.bind("<Button-1>", app.on_window_click)

def on_menu_motion(app, event):
    menu_labels = ["文件", "模型", "数据", "训练", "推理", "关于"]
    menus = [app.file_menu, app.model_menu, app.data_menu,
             app.train_menu, app.infer_menu, app.about_menu]

    for i, (label, submenu) in enumerate(zip(menu_labels, menus)):
        bbox = app.menubar.bbox(i)
        if bbox:
            x1, y1, x2, y2 = bbox
            if x1 <= event.x <= x2 and y1 <= event.y <= y2:
                if app.current_submenu and app.current_submenu != submenu:
                    app.current_submenu.unpost()
                app.current_submenu = submenu
                submenu.post(app.winfo_rootx() + x1, app.winfo_rooty() + y2)
                return

    if app.current_submenu:
        app.current_submenu.unpost()
        app.current_submenu = None

def on_submenu_leave(app, event):
    if not (app.menubar.winfo_containing(event.x_root, event.y_root) or
            any(menu.winfo_containing(event.x_root, event.y_root)
                for menu in [app.file_menu, app.model_menu, app.data_menu,
                             app.train_menu, app.infer_menu, app.about_menu])):
        if app.current_submenu:
            app.current_submenu.unpost()
            app.current_submenu = None

def on_window_click(app, event):
    if app.current_submenu:
        app.current_submenu.unpost()
        app.current_submenu = None

# 最近文件功能
def load_recent_files(shared_data):
    """从文件加载最近打开的文件列表"""
    try:
        if os.path.exists(shared_data.recent_file_path):
            with open(shared_data.recent_file_path, 'r', encoding='utf-8') as f:
                shared_data.recent_files = json.load(f)
                shared_data.recent_files = [f for f in shared_data.recent_files if os.path.exists(f)]
    except Exception as e:
        print(f"加载最近文件失败: {e}")
        shared_data.recent_files = []

def save_recent_files(shared_data):
    """保存最近文件列表到本地"""
    try:
        with open(shared_data.recent_file_path, 'w', encoding='utf-8') as f:
            json.dump(shared_data.recent_files, f, ensure_ascii=False, indent=2)
    except Exception as e:
        print(f"保存最近文件失败: {e}")

def add_to_recent_files(shared_data, file_path):
    """添加文件到最近列表"""
    if file_path in shared_data.recent_files:
        shared_data.recent_files.remove(file_path)
    shared_data.recent_files.insert(0, file_path)
    if len(shared_data.recent_files) > 5:
        shared_data.recent_files = shared_data.recent_files[:5]
    save_recent_files(shared_data)
    update_recent_files_menu(shared_data, shared_data.ui_elements["app"])

def update_recent_files_menu(shared_data, app):
    """更新最近文件子菜单"""
    app.recent_files_menu.delete(0, tk.END)

    if not shared_data.recent_files:
        app.recent_files_menu.add_command(label="无最近文件", state="disabled")
    else:
        for file_path in shared_data.recent_files:
            app.recent_files_menu.add_command(
                label=os.path.basename(file_path),
                command=lambda fp=file_path: open_recent_file(shared_data, app, fp)
            )

    app.recent_files_menu.add_separator()
    app.recent_files_menu.add_command(label="清空最近文件", command=lambda: clear_recent_files(shared_data, app))

def open_recent_file(shared_data, app, file_path):
    """打开最近文件"""
    if not os.path.exists(file_path):
        messagebox.showwarning("文件不存在", f"文件已不存在：\n{file_path}\n已从列表中移除。")
        shared_data.recent_files.remove(file_path)
        save_recent_files(shared_data)
        update_recent_files_menu(shared_data, app)
        return

    ext = os.path.splitext(file_path)[1].lower()
    if ext in [".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tiff"]:
        cmd_open_image_by_path(shared_data, app, file_path)
    elif ext in [".mp4", ".avi", ".mov", ".mkv", ".flv", ".wmv"]:
        cmd_open_video_by_path(shared_data, app, file_path)
    else:
        messagebox.showerror("不支持的文件类型", "仅支持图片和视频文件。")

# 文件操作命令

def cmd_open_folder(shared_data, app):
    """打开文件夹，加载其中所有支持的图片文件到文件列表"""
    stop_video(shared_data, app)  # 停止播放视频或摄像头

    # 弹出文件夹选择对话框
    folder_path = filedialog.askdirectory(title="选择图片文件夹")
    if not folder_path or not os.path.exists(folder_path):
        return

    # 支持的图片格式
    valid_exts = {".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".gif"}

    # 获取文件夹中所有图片文件（按名称排序）
    image_files = []
    for file_name in sorted(os.listdir(folder_path)):  # 排序使顺序一致
        ext = os.path.splitext(file_name)[1].lower()
        if ext in valid_exts:
            file_path = os.path.join(folder_path, file_name)
            if os.path.isfile(file_path):
                image_files.append(file_path)

    if not image_files:
        app.show_status("该文件夹中没有找到支持的图片文件")
        messagebox.showinfo("提示", "未在文件夹中找到支持的图片文件。")
        return

    # 更新共享数据 👈 MODIFIED: 只使用 image_paths
    shared_data.image_paths = image_files
    shared_data.current_image_index = 0
    shared_data.current_source = "picture"  # 👈 MODIFIED: 设置源类型为图片

    # 清空并填充文件列表
    file_listbox = shared_data.ui_elements["file_listbox"]
    file_listbox.delete(0, tk.END)
    for path in image_files:
        file_listbox.insert(tk.END, os.path.basename(path))
        add_to_recent_files(shared_data, path)  # 添加到最近文件（路径）

    # 显示第一张图片
    first_file = image_files[0]
    try:
        image = Image.open(first_file)
        display_image_on_canvas(image, shared_data.ui_elements["source_canvas"])
        app.show_status(f"已打开文件夹: {os.path.basename(folder_path)} ({len(image_files)} 张图片)")
        file_listbox.selection_set(0)  # 选中第一个
        file_listbox.see(0)           # 确保可见
        file_listbox.focus_set()      # 获取焦点
    except Exception as e:
        app.show_status(f"打开首张图片失败: {str(e)}")
        return

def cmd_open_image(shared_data, app):
    """打开图片（支持多选）"""
    stop_video(shared_data, app)
    file_paths = filedialog.askopenfilenames(
        title="选择图片文件",
        filetypes=[("图片文件", "*.jpg *.jpeg *.png *.bmp *.gif *.tiff")]
    )
    if not file_paths:
        return

    # 清空之前的数据 👈 MODIFIED: 只使用 image_paths
    shared_data.image_paths = list(file_paths)
    shared_data.current_image_index = 0
    shared_data.current_source = "picture"  # 👈 MODIFIED: 设置源类型为图片

    # 清空并填充文件列表
    file_listbox = shared_data.ui_elements["file_listbox"]
    file_listbox.delete(0, tk.END)
    for path in shared_data.image_paths:
        file_listbox.insert(tk.END, os.path.basename(path))
        add_to_recent_files(shared_data, path)

    # 显示第一个图片
    first_file = shared_data.image_paths[0]
    try:
        image = Image.open(first_file)
        display_image_on_canvas(image, shared_data.ui_elements["source_canvas"])
        app.show_status(f"已打开图片: {os.path.basename(first_file)}")
        file_listbox.selection_set(0)          # 选中第一项
        file_listbox.see(0)                    # 确保可见
        file_listbox.focus_set()               # 获取键盘焦点
    except Exception as e:
        app.show_status(f"打开图片失败: {str(e)}")
        return

def cmd_open_image_by_path(shared_data, app, file_path):
    """通过路径打开图片"""
    stop_video(shared_data, app)
    try:
        image = Image.open(file_path)
        display_image_on_canvas(image, shared_data.ui_elements["source_canvas"])
        app.show_status(f"已打开图片: {os.path.basename(file_path)}")
        add_to_recent_files(shared_data, file_path)
        
        # 👈 MODIFIED: 当通过路径打开单个图片时，也更新列表和状态
        shared_data.image_paths = [file_path]  # 单个图片也作为列表
        shared_data.current_image_index = 0
        shared_data.current_source = "picture"
        
        # 更新文件列表
        file_listbox = shared_data.ui_elements["file_listbox"]
        file_listbox.delete(0, tk.END)
        file_listbox.insert(tk.END, os.path.basename(file_path))
        file_listbox.selection_set(0)
        file_listbox.focus_set()
        
    except Exception as e:
        app.show_status(f"打开图片失败: {str(e)}")

def cmd_open_video(shared_data, app):
    """打开视频（单选）"""
    stop_video(shared_data, app)
    file_path = filedialog.askopenfilename(
        title="选择视频文件",
        filetypes=[("视频文件", "*.mp4 *.avi *.mov *.mkv *.flv *.wmv")]
    )
    if not file_path:
        return
    cmd_open_video_by_path(shared_data, app, file_path)

# def cmd_open_video_by_path(shared_data, app, file_path):
#     """通过路径打开视频，并将其添加到文件列表"""
#     stop_video(shared_data, app)

#     # 检查文件是否存在
#     if not os.path.exists(file_path):
#         app.show_status("视频文件不存在")
#         messagebox.showerror("错误", "视频文件不存在")
#         return

#     # 👈 MODIFIED: 将视频路径也存入 image_paths，并设置 current_source
#     shared_data.image_paths = [file_path]  # 单个视频也作为列表处理
#     shared_data.current_image_index = 0
#     shared_data.current_source = "video"  # 👈 MODIFIED: 设置源类型为视频

#     # 获取文件列表控件
#     file_listbox = shared_data.ui_elements["file_listbox"]
#     file_listbox.delete(0, tk.END)
#     file_listbox.insert(tk.END, os.path.basename(file_path))  # 显示文件名
#     file_listbox.selection_set(0)
#     file_listbox.see(0)
#     file_listbox.focus_set()

#     # 打开视频
#     shared_data.cap = cv2.VideoCapture(file_path)
#     if not shared_data.cap.isOpened():
#         app.show_status("无法打开视频文件")
#         messagebox.showerror("错误", "无法打开视频文件")
#         shared_data.cap = None
#         return

#     shared_data.video_playing = True
#     shared_data.source_path = file_path
#     shared_data.source_type = "video"

#     app.show_status(f"正在播放视频: {os.path.basename(file_path)}")

#     # 开始播放
#     update_video_frame(shared_data, app)

#     # 添加到最近文件
#     add_to_recent_files(shared_data, file_path)

def cmd_open_video_by_path(shared_data, app, file_path):
    """通过路径打开视频，并将其添加到文件列表"""
    stop_video(shared_data, app)
    # 检查文件是否存在
    if not os.path.exists(file_path):
        app.show_status("视频文件不存在")
        messagebox.showerror("错误", "视频文件不存在")
        return

    # ✅ 修改：将路径存入 video_paths
    shared_data.video_paths = [file_path]  # 单个视频也作为列表处理
    shared_data.current_video_index = 0
    shared_data.current_source = "video"  # 设置源类型为视频

    # 获取文件列表控件
    file_listbox = shared_data.ui_elements["file_listbox"]
    file_listbox.delete(0, tk.END)
    file_listbox.insert(tk.END, os.path.basename(file_path))  # 显示文件名
    file_listbox.selection_set(0)
    file_listbox.see(0)
    file_listbox.focus_set()

    # 打开视频
    shared_data.cap = cv2.VideoCapture(file_path)
    if not shared_data.cap.isOpened():
        app.show_status("无法打开视频文件")
        messagebox.showerror("错误", "无法打开视频文件")
        shared_data.cap = None
        return
    shared_data.video_playing = True
    app.show_status(f"正在播放视频: {os.path.basename(file_path)}")

    # 开始播放
    update_video_frame(shared_data, app)
    add_to_recent_files(shared_data, file_path)

def cmd_open_camera(shared_data, app):
    """打开摄像头"""
    stop_video(shared_data, app)
    devices = ["摄像头 0 (默认)", "摄像头 1", "摄像头 2"]
    device_choice = choose_device_dialog(app, devices)
    if device_choice is None:
        return

    camera_index = int(device_choice.split()[1])
    shared_data.cap = cv2.VideoCapture(camera_index)
    if not shared_data.cap.isOpened():
        app.show_status(f"无法打开摄像头 {camera_index}")
        return
        
    # 👈 MODIFIED: 打开摄像头时，不需要实际的文件路径，但要设置 current_source
    # 由于摄像头不是文件，我们清空 image_paths，但 current_source 仍为 "camera"
    shared_data.image_paths = []  # 👈 MODIFIED: 摄像头没有文件路径
    shared_data.current_image_index = -1
    shared_data.current_source = "camera"  # 👈 MODIFIED: 设置源类型为摄像头
    
    shared_data.video_playing = True
    app.show_status(f"摄像头 {camera_index} 已开启")
    update_video_frame(shared_data, app)
    
    # 注意：这里添加的 "camera_x.mp4" 是一个虚拟路径，用于记录操作，非真实文件
    add_to_recent_files(shared_data, f"camera_{camera_index}.mp4")

def choose_device_dialog(app, devices):
    """弹出设备选择对话框"""
    dialog = tk.Toplevel(app)
    dialog.title("选择摄像头设备")
    dialog.geometry("300x200")
    dialog.transient(app)
    dialog.grab_set()

    tk.Label(dialog, text="请选择摄像头设备：").pack(pady=10)

    var = tk.StringVar(value=devices[0])
    for device in devices:
        rb = tk.Radiobutton(dialog, text=device, variable=var, value=device)
        rb.pack(anchor=tk.W, pady=5)

    result = [None]

    def confirm():
        result[0] = var.get()
        dialog.destroy()

    def cancel():
        dialog.destroy()

    tk.Button(dialog, text="确定", command=confirm).pack(pady=10)
    tk.Button(dialog, text="取消", command=cancel).pack(pady=10)

    dialog.wait_window()
    return result[0]

def quit_application(shared_data, app, event=None):
    """退出应用程序"""
    save_recent_files(shared_data)
    stop_video(shared_data, app)

    if messagebox.askokcancel("退出", "确定要退出程序吗？"):
        app.destroy()

# 视频相关操作
def update_video_frame(shared_data, app):
    """定时更新视频帧"""
    if not shared_data.video_playing or not shared_data.cap or not shared_data.cap.isOpened():
        return

    ret, frame = shared_data.cap.read()
    if not ret:
        app.show_status("播放结束")
        stop_video(shared_data, app)
        return

    frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    pil_image = Image.fromarray(frame_rgb)
    display_image_on_canvas(pil_image, shared_data.ui_elements["source_canvas"])
    app.after(33, lambda: update_video_frame(shared_data, app))

def stop_video(shared_data, app):
    """停止视频或摄像头播放"""
    if shared_data.cap:
        shared_data.cap.release()
        shared_data.cap = None
    shared_data.video_playing = False

    source_canvas = shared_data.ui_elements["source_canvas"]
    source_canvas.delete("all")
    source_canvas.create_text(
        source_canvas.winfo_width() // 2 or 300,
        source_canvas.winfo_height() // 2 or 200,
        text="播放已停止\n请重新选择文件或摄像头",
        fill="gray",
        font=("SimHei", 12) if "SimHei" in tk.font.families() else ("Arial", 12),
        tags="placeholder"
    )

# 文件列表操作
def on_file_select(shared_data, app, event):
    """文件列表选择事件（支持图片和视频）"""
    selection = shared_data.ui_elements["file_listbox"].curselection()
    if not selection:
        return
    index = selection[0]

    # 防止重复加载
    if hasattr(shared_data, 'current_image_index') and index == shared_data.current_image_index:
        return

    shared_data.current_image_index = index
    file_path = shared_data.image_paths[index]

    if not os.path.exists(file_path):
        app.show_status("文件不存在")
        messagebox.showwarning("警告", f"文件不存在：\n{file_path}")
        return

    # 判断文件类型
    ext = os.path.splitext(file_path)[1].lower()
    if ext in [".mp4", ".avi", ".mov", ".mkv", ".flv", ".wmv"]:  # 视频
        stop_video(shared_data, app)  # 停止当前播放
        cmd_open_video_by_path(shared_data, app, file_path)
    elif ext in [".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".gif"]:  # 图片
        stop_video(shared_data, app)  # 停止视频
        try:
            image = Image.open(file_path)
            display_image_on_canvas(image, shared_data.ui_elements["source_canvas"])
            shared_data.source_path = file_path
            shared_data.source_type = "image"
            app.show_status(f"显示图片: {os.path.basename(file_path)}")
        except Exception as e:
            app.show_status(f"加载图片失败: {str(e)}")
            messagebox.showerror("错误", f"无法加载图片：\n{str(e)}")
    else:
        app.show_status("不支持的文件格式")

def on_key_up(shared_data, app, event):
    """键盘上键事件"""
    file_listbox = shared_data.ui_elements["file_listbox"]
    current = file_listbox.curselection()
    if not current:
        return
    index = current[0]
    if index > 0:
        file_listbox.selection_clear(index)
        file_listbox.selection_set(index - 1)
        file_listbox.see(index - 1)
        on_file_select(shared_data, app, None)

def on_key_down(shared_data, app, event):
    """键盘下键事件"""
    file_listbox = shared_data.ui_elements["file_listbox"]
    current = file_listbox.curselection()
    if not current:
        return
    index = current[0]
    if index < file_listbox.size() - 1:
        file_listbox.selection_clear(index)
        file_listbox.selection_set(index + 1)
        file_listbox.see(index + 1)
        on_file_select(shared_data, app, None)

def on_file_delete(shared_data, app, event):
    """Delete键或菜单命令：从文件列表中删除选中项"""
    file_listbox = shared_data.ui_elements["file_listbox"]
    selection = file_listbox.curselection()
    if not selection:
        messagebox.showinfo("提示", "请先在文件列表中选择一个项目。")
        return

    index = selection[0]
    file_name = file_listbox.get(index)

    # 弹出确认对话框
    if not messagebox.askyesno("确认删除", f"确定要从列表中删除 “{file_name}” 吗？\n（不会删除磁盘文件）"):
        return

    # 判断当前是图片列表还是视频列表
    if hasattr(shared_data, 'image_paths') and shared_data.image_paths and index < len(shared_data.image_paths):
        current_paths = shared_data.image_paths
    elif hasattr(shared_data, 'video_paths') and shared_data.video_paths and index < len(shared_data.video_paths):
        current_paths = shared_data.video_paths
    else:
        app.show_status("无有效文件路径")
        return

    # 删除路径和列表项
    deleted_path = current_paths.pop(index)
    file_listbox.delete(index)

    total = len(current_paths)
    if total == 0:
        # 清空画布
        source_canvas = shared_data.ui_elements["source_canvas"]
        source_canvas.delete("all")
        source_canvas.create_text(
            source_canvas.winfo_width() // 2 or 300,
            source_canvas.winfo_height() // 2 or 200,
            text="无文件\n请打开图片或视频",
            fill="gray",
            font=("SimHei", 12) if "SimHei" in tk.font.families() else ("Arial", 12),
            tags="placeholder"
        )
        shared_data.current_image_index = -1
        shared_data.current_source = None
        app.show_status("文件列表已清空")
        return

    # 更新当前索引：优先选中下一个，若无则选中上一个
    new_index = index if index < total else total - 1
    shared_data.current_image_index = new_index
    file_listbox.selection_set(new_index)
    file_listbox.see(new_index)

    # 如果删除的是正在播放的视频，需要停止
    if shared_data.current_source == "video" and hasattr(shared_data, 'cap') and shared_data.cap:
        current_video_path = shared_data.video_paths[0] if shared_data.video_paths else None
        if current_video_path == deleted_path:
            stop_video(shared_data, app)
            app.show_status(f"已停止播放并删除: {file_name}")
            return

    # 显示新选中项（如果是图片）
    if shared_data.current_source == "picture" and current_paths:
        try:
            image = Image.open(current_paths[new_index])
            display_image_on_canvas(image, shared_data.ui_elements["source_canvas"])
            app.show_status(f"显示图片: {os.path.basename(current_paths[new_index])}")
        except Exception as e:
            app.show_status(f"加载图片失败: {str(e)}")
    else:
        app.show_status(f"已从列表删除: {file_name}")

def clear_recent_files(shared_data, app):
    """清空最近文件列表"""
    shared_data.recent_files = []
    save_recent_files(shared_data)
    update_recent_files_menu(shared_data, app)
    app.show_status("最近文件列表已清空")

def cmd_save_result(shared_data, app):
    """保存当前选中的预测结果文件（从内存中保存）—— 支持保存为视频"""
    # 检查是否有预测结果（内存中）
    if not hasattr(shared_data, 'pred_results') or not shared_data.pred_results:
        app.show_status("没有可用的预测结果")
        messagebox.showwarning("警告", "当前没有预测结果可供保存。")
        return

    # 获取当前选中的预测文件
    pred_listbox = shared_data.ui_elements["pred_listbox"]
    selection = pred_listbox.curselection()
    if not selection:
        app.show_status("请先在列表中选择一个预测结果")
        messagebox.showinfo("提示", "请先在预测结果列表中选择一个文件。")
        return

    index = selection[0]
    filename, pil_image = shared_data.pred_results[index]

    # 判断是否是视频预测结果（通过是否有 frame_results 且文件名包含 "_pred"）
    is_video_result = (
        hasattr(shared_data, 'frame_results') and
        len(shared_data.frame_results) > 1 and
        "_pred" in filename
    )

    if is_video_result:
        # 👉 保存为视频
        default_name = os.path.splitext(filename)[0] + ".mp4"
        save_path = filedialog.asksaveasfilename(
            title="保存预测视频",
            initialfile=default_name,
            defaultextension=".mp4",
            filetypes=[
                ("MP4 Video", "*.mp4"),
                ("AVI Video", "*.avi"),
                ("All files", "*.*")
            ]
        )

        if not save_path:
            app.show_status("保存操作已取消")
            return

        try:
            app.show_status("正在保存视频...")
            app.update_idletasks()  # 强制刷新界面

            # 获取第一帧尺寸
            first_frame = shared_data.frame_results[0]
            width, height = first_frame.size

            # 设置视频编码器和写入器
            fourcc = cv2.VideoWriter_fourcc(*'mp4v')  # 或 'XVID' for .avi
            if save_path.lower().endswith('.avi'):
                fourcc = cv2.VideoWriter_fourcc(*'XVID')

            fps = 30  # 可根据原视频动态获取，这里默认30
            if hasattr(shared_data, 'source_fps') and shared_data.source_fps > 0:
                fps = shared_data.source_fps

            video_writer = cv2.VideoWriter(save_path, fourcc, fps, (width, height))

            if not video_writer.isOpened():
                raise Exception("无法创建视频写入器")

            # 写入每一帧
            for i, pil_img in enumerate(shared_data.frame_results):
                # PIL.Image → OpenCV BGR
                rgb_img = pil_img.convert("RGB")
                opencv_img = cv2.cvtColor(np.array(rgb_img), cv2.COLOR_RGB2BGR)
                video_writer.write(opencv_img)

                # 可选：更新状态（每10帧）
                if i % 10 == 0:
                    app.show_status(f"正在保存视频帧 {i+1}/{len(shared_data.frame_results)}...")
                    app.update_idletasks()

            video_writer.release()

            app.show_status(f"视频已保存至: {os.path.basename(save_path)}")
            messagebox.showinfo("成功", f"视频已成功保存为：\n{save_path}")

        except Exception as e:
            app.show_status(f"保存视频失败: {str(e)}")
            messagebox.showerror("保存失败", f"无法保存视频：\n{str(e)}")
            import traceback
            traceback.print_exc()

    else:
        # 👉 保存为图片（原逻辑）
        default_name = os.path.splitext(filename)[0] + "_pred.jpg"
        save_path = filedialog.asksaveasfilename(
            title="保存预测结果",
            initialfile=default_name,
            defaultextension=".jpg",
            filetypes=[
                ("JPEG", "*.jpg"),
                ("PNG", "*.png"),
                ("BMP", "*.bmp"),
                ("All files", "*.*")
            ]
        )

        if not save_path:
            app.show_status("保存操作已取消")
            return

        try:
            pil_image.save(save_path)
            app.show_status(f"结果已保存至: {os.path.basename(save_path)}")
            messagebox.showinfo("成功", f"文件已成功保存为：\n{save_path}")
        except Exception as e:
            app.show_status(f"保存失败: {str(e)}")
            messagebox.showerror("保存失败", f"无法保存文件：\n{str(e)}")

def cmd_batch_detect(shared_data, app):
    """对文件列表中选中的图片执行批量YOLO检测，结果保存在内存中，不默认保存到磁盘"""
    from ultralytics import YOLO
    import os
    import torch

    # 获取文件列表控件和选中项
    file_listbox = shared_data.ui_elements["file_listbox"]
    selection = file_listbox.curselection()
    if not selection:
        app.show_status("请先在文件列表中选择一个或多个图片进行检测")
        messagebox.showwarning("警告", "没有选择任何文件。")
        return

    # 获取选中的文件路径
    selected_image_paths = [shared_data.image_paths[i] for i in selection]
    image_files = []
    unsupported_files = []

    # 过滤支持的图像格式
    valid_exts = {".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".gif"}
    for path in selected_image_paths:
        ext = os.path.splitext(path)[1].lower()
        if ext in valid_exts:
            image_files.append(path)
        else:
            unsupported_files.append(os.path.basename(path))

    if not image_files:
        app.show_status("没有有效的图片文件可供检测")
        msg = "选中的文件中不包含支持的图片格式。" + (f"\n（忽略: {', '.join(unsupported_files)}）" if unsupported_files else "")
        messagebox.showwarning("警告", msg)
        return

    # 模型路径
    model_path = os.path.abspath(
        os.path.join(
            os.path.dirname(__file__), "..", "results", "result111", "train","weights", "best.pt"
        )
    )
    if not os.path.exists(model_path):
        app.show_status(f"错误：模型文件不存在 -> {model_path}")
        messagebox.showerror("错误", f"未找到模型文件：\n{model_path}\n请确认路径正确。")
        return

    try:
        app.show_status("正在加载 YOLO 模型...")
        model = YOLO(model_path)

        app.show_status(f"开始批量检测 ({len(image_files)} 个文件)...")
        results = model(
            source=image_files,
            save=False,
            imgsz=shared_data.model_config["image_size"],     # ✅ 使用配置中的图像大小
            conf=shared_data.model_config["confidence"],       # ✅ 使用 UI 设置的置信度
            iou=shared_data.model_config["iou"],               # ✅ 顺手也用上 IOU
            device=0 if torch.cuda.is_available() else "cpu"
        )

        # 存储结果：(filename, PIL Image)
        pred_results = []
        for i, result in enumerate(results):
            try:
                plotted = result.plot()  # BGR 格式
                rgb_image = cv2.cvtColor(plotted, cv2.COLOR_BGR2RGB)  # 转为 RGB
                pil_img = Image.fromarray(rgb_image)
                src_name = os.path.basename(image_files[i])
                pred_results.append((src_name, pil_img))
            except Exception as e:
                app.show_status(f"处理 {src_name} 失败: {e}")
                continue

        if not pred_results:
            app.show_status("未生成任何预测结果")
            messagebox.showwarning("警告", "推理完成但未生成有效结果。")
            return

        # 更新 shared_data
        shared_data.pred_results = pred_results  # 存储内存图像

        # 更新预测结果列表 UI
        pred_listbox = shared_data.ui_elements["pred_listbox"]
        pred_listbox.delete(0, tk.END)
        for name, _ in pred_results:
            pred_listbox.insert(tk.END, name)

        # 显示第一张
        try:
            _, first_img = pred_results[0]
            display_image_on_canvas(first_img, shared_data.ui_elements["result_canvas"])
        except Exception as e:
            app.show_status(f"无法显示第一张结果: {e}")

        app.show_status(f"批量检测完成，共 {len(pred_results)} 个结果（未保存）")
        messagebox.showinfo("完成", f"成功处理 {len(image_files)} 个文件\n结果已加载到内存，点击“保存结果”可导出。")

    except Exception as e:
        app.show_status(f"批量检测失败: {str(e)}")
        messagebox.showerror("错误", f"检测过程中出错：\n{str(e)}")
        import traceback
        traceback.print_exc()

